package qc.module.qms.service.ynsyjpt;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import qc.common.core.exception.QCPromptException;
import qc.common.core.json.JsonParserUtil;
import qc.common.core.utils.DateUtil;
import qc.common.core.utils.SqlRunnerUtil;
import qc.common.core.utils.TimeSpanUtil;
import qc.module.platform.dto.dbTable.DBTableFullDto;
import qc.module.qms.dto.station.StationDto;
import qc.module.qms.service.exchange.ExchangeDataService;

import java.sql.SQLException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 云南省汇集平台实时水位库容  数据生成器
 *
 * @author QuCheng Tech
 * @since 2024/9/25
 */
@Service
public class YnsyjptRsvrDataGenerator {
    @Autowired
    private ExchangeDataService exchangeDataService;

    /**
     * 生成目标时间段内省汇集平台的水位库容数据
     *
     * @param station 站点信息，需要站点参数信息
     * @param beginTime 起始时间
     * @param endTime 截止时间
     * @param destDbTable 目标数据表信息，包含表名称和数据库连接信息
     * @param srcDbTable 原始数据表信息，包含表名称和数据库连接信息
     * @return java.lang.String
     * @author QuCheng Tech
     * @since 2024/9/25
     */
    public String generate(StationDto station, Date beginTime, Date endTime, DBTableFullDto destDbTable, DBTableFullDto srcDbTable) throws QCPromptException, SQLException {
        /**
         * 处理的逻辑和步骤
         * 1.判断输入和从输入中获取信息
         *      1.1.站点信息必须有，从站点中获取原始数据的站号、上传使用的水库注册号、appid、appkey
         *      1.2.起止时间段必须正确
         *      1.3.目标数据库表信息，从中获取数据库连接信息和表名
         *      1.4.原始数据库信息，从中获取原始数据库连接信息和表名
         * 2.遍历起止时间点，逐个时间点进行数据处理
         *      2.1.需要每个时间点判断所在月份，得到间隔的分钟数；
         *      2.2.水位数据值处理：
         *      2.3.库容数据值处理：
         *
         * 返回结果为null时表示成功且没有警告，返回结果不为null表示警告信息，如果验证不通过或无法进行处理的抛出异常
         */

        Date now = DateUtil.getNowDate();
        StringBuilder sbResultInfo = new StringBuilder();

        //步骤1.1，站点信息判断和数据获取
        if (station == null)
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，站点信息对象不能为空");
        if (StringUtils.isBlank(station.getStcd()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，站点编码不能为空");
        if (StringUtils.isBlank(station.getParams()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，站点参数不能为空");
        //此处的stcd为交换数据表中的目标stcd，查询原始数据需要从站点参数中获取
        String stcd = station.getStcd();
        //原始数据对应的stcd，默认与目标stcd相同，从站点参数中获取
        String sourceStcd = stcd;
        //水库注册号
        String rscd = null;
        //每个水库的appid和appkey
        String appid = null;
        String appkey = null;
        //初始化上传数据的标记为0--未上传，可以从站点的配置中获取初始的标记状态，对不需要上传的站点可以在站点信息中配置为无需上传
        int flag = 0x0;

        System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " getParams=" + station.getParams());
        sourceStcd = JsonParserUtil.getString(station.getParams(), "run>原始数据站号");
        appid = JsonParserUtil.getString(station.getParams(), "run>id");
        appkey = JsonParserUtil.getString(station.getParams(), "run>key");
        System.out.println("获取站点参数中的原始数据站号，stcd=" + stcd + " sourceStcd=" + sourceStcd);
        if (StringUtils.isBlank(sourceStcd))
            sourceStcd = stcd;
        rscd = JsonParserUtil.getString(station.getParams(), "run>水库注册号");
        //获取站点配置中的flag配置
        flag = JsonParserUtil.getIntValue(station.getParams(), "run>flag", 0x0);
        //判断水库注册号、appid、appkey均不能为空
        if (StringUtils.isBlank(rscd))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，水库注册号不能为空");
        if (StringUtils.isBlank(appid))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，AppId不能为空");
        if (StringUtils.isBlank(appkey))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，AppKey不能为空");
        //数据间隔频率，分钟
        int dataIntervalMinutes = JsonParserUtil.getIntValue(station.getParams(), "run>上报水位频率分钟", 0x5);
        //获取站点中水位和库容计算配置
        double waterHeight = 0.0f;
        double waterMin = 0.0f;
        double wa = 1.0f;
        double wb = 0.0f;
        boolean isWaterDepthValue = true;//是否为水深值，默认true
        if (station != null && StringUtils.isNotBlank(station.getParams())) {
            isWaterDepthValue = JsonParserUtil.getBooleanValue(station.getParams(), "run>原始水位为水深值", true);
            waterHeight = JsonParserUtil.getDoubleValue(station.getParams(), "run>水位高程", 0.0f);
            waterMin = JsonParserUtil.getDoubleValue(station.getParams(), "run>坝底高程", 0.0f);
            wa = JsonParserUtil.getDoubleValue(station.getParams(), "run>库容系数", 1.0f);
            wb = JsonParserUtil.getDoubleValue(station.getParams(), "run>库容尾数", 0.0f);
        }

        //步骤1.2.起止时间段
        if (beginTime.after(endTime))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，起始时间不能大于截止时间");
        //增加判断时间不能距离现在时间太久
        //截止时间不能当前时间后1小时
        //开始时间不能小于当前时间前2月
        if (endTime.after(DateUtil.addMinutes(now, 60)))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，截止时间不能大于当前时间后60分钟");
        if (beginTime.before(DateUtil.addMonths(now, -2)))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，起始时间不能大于当前时间前2月");
        //时间点需要根据时段长度获取时段的起始时间
        int beginTimeMonth = DateUtil.getMonth(beginTime);
        //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
        Date beginHourTime = TimeSpanUtil.getBeginTime(beginTime, 5);
        if (beginTimeMonth > 10 || beginTimeMonth < 5)
            beginHourTime = TimeSpanUtil.getBeginTime(beginTime, 60);
        //根据传入的截止时间点计算时段末时间
        int endTimeMonth = DateUtil.getMonth(endTime);
        //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
        Date endHourTime = TimeSpanUtil.getEndTime(endTime, 5);
        if (endTimeMonth > 10 || endTimeMonth < 5)
            endHourTime = TimeSpanUtil.getEndTime(endTime, 60);
        if (endHourTime.after(now)){
            if (endTimeMonth > 10 || endTimeMonth < 5)
                endHourTime = DateUtil.addMinutes(endTime, -60);
            else endHourTime = DateUtil.addMinutes(endTime, -5);
        }

        //步骤1.3.目标数据库表信息判断和数据获取
        if (destDbTable == null)
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，目标数据库表对象不能为空");
        if (destDbTable.getTable() == null || StringUtils.isBlank(destDbTable.getTable().getCode()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，目标数据库表名称不能为空");
        if (destDbTable.getDb() == null || StringUtils.isBlank(destDbTable.getDb().getCon()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，目标数据库表连接信息不能为空");
        String destDbTableCode = destDbTable.getTable().getCode();
        String destConJsonString = destDbTable.getDb().getCon();
        String destDbUrl = JsonParserUtil.getString(destConJsonString, "url");
        String destDbUser = JsonParserUtil.getString(destConJsonString, "user");
        String destDbPassword = JsonParserUtil.getString(destConJsonString, "password");

        //步骤1.4.原始数据库表信息判断和数据获取
        if (srcDbTable == null)
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，原始数据库表对象不能为空");
        if (srcDbTable.getTable() == null || StringUtils.isBlank(srcDbTable.getTable().getCode()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，原始数据库表名称不能为空");
        if (srcDbTable.getDb() == null || StringUtils.isBlank(srcDbTable.getDb().getCon()))
            throw new QCPromptException("云南省汇集平台实时水位库容数据生成器处理，原始数据库表连接信息不能为空");
        String srcDbTableCode = srcDbTable.getTable().getCode();
        String srcConJsonString = srcDbTable.getDb().getCon();
        String srcDbUrl = JsonParserUtil.getString(srcConJsonString, "url");
        String srcDbUser = JsonParserUtil.getString(srcConJsonString, "user");
        String srcDbPassword = JsonParserUtil.getString(srcConJsonString, "password");

        //实时水位库容数据的目标时间段与查询原始数据的时间段一致，不需要进行时间偏移
        //2024-9-29，原始数据表由DeviceTimingDataRecord修改为MS_QZJ_SRCDATA；计讯接收软件接收到数据后写入DeviceTimingDataRecord，由触发器触发到MS_QZJ_SRCDATA
        //使用DeviceTimingDataRecord作为原始数据表时的查询条件和查询列
        //LinkedHashMap<Date, Double> sourceData = exchangeDataService.getData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "inserttime", "yczno", "timingvalue1", sourceStcd, beginHourTime, endHourTime);
        //使用MS_QZJ_SRCDATA作为原始数据表时的查询条件和查询列
        //查询原始数据除站点编码和时间段外还需要的其他等于条件
        Map<String, Object> otherEqualWhereData = new HashMap<>();
        otherEqualWhereData.put("DATATYPE", "water");
        LinkedHashMap<Date, Double> sourceData = exchangeDataService.getData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "TM", "STCD", "VALUE", sourceStcd, beginHourTime, endHourTime, otherEqualWhereData);
        //如果没有原始数据，可以进行特殊处理，水位数据使用最新延续，雨量可以默认为0
        if (sourceData == null || sourceData.size() < 0x1) {
            //水位数据均先查询一次开始时间前的最近一条数据
            sourceData = new LinkedHashMap<>();//解决空指针异常：sourceData为null，sourceData.put直接会报出空指针异常
            //使用DeviceTimingDataRecord作为原始数据表时的查询条件和查询列
            //LinkedHashMap<Date, Double> latestSourceData = exchangeDataService.getLatestData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "inserttime", "yczno", "timingvalue1", sourceStcd, beginHourTime);
            //使用MS_QZJ_SRCDATA作为原始数据表时的查询条件和查询列
            LinkedHashMap<Date, Double> latestSourceData = exchangeDataService.getLatestData(srcDbUrl, srcDbUser, srcDbPassword, srcDbTableCode, "TM", "STCD", "VALUE", sourceStcd, beginHourTime, otherEqualWhereData);
            if (latestSourceData != null && latestSourceData.size() > 0x0) {
                //最新1条数据查询使用top 1 order by tm desc查询，只需要添加查询结果的第一条数据
                Map.Entry<Date, Double> firstEntry = latestSourceData.entrySet().iterator().next();
                sourceData.put(firstEntry.getKey(), firstEntry.getValue());
            } else {
                //取最新原始数据还是没有，可以跳过当前站点
                System.out.println("处理水库水位库容，stcd=" + stcd + " 原始采集数据和最新原始数据均不存在，无法进行当前时间点的水位和库容计算");
                throw new QCPromptException("处理水库水位库容，stcd=" + stcd + " 原始采集数据和最新原始数据均不存在，无法进行当前时间点的水位和库容计算");
            }
        }

        System.out.println("处理水库水位库容，stcd=" + stcd + "，起始时间=" + DateUtil.getDateTimeString(beginHourTime) + "，截止时间=" + DateUtil.getDateTimeString(endHourTime));

        //传入的起始时间就是要生成数据的第1个时间点，不需要往后1个时段
        Date date = beginHourTime;
        //需要从计算的时段初开始时间点到截止时间点，包含截止时间点；判断条件为：后移的时间点不大于截止时间点
        while (!date.after(endHourTime)) {
            //获取对应时间点的原始数据，如果对应时间点有值取时间点的值，如果没有对应时间点的值往前取最近一条
            double sourceDataValue = Double.NaN;
            if (sourceData != null && sourceData.size() > 0x0) {
                Date finalDate = date;
                //取对应时间点
                if (sourceData.containsKey(finalDate))
                    sourceDataValue = sourceData.get(finalDate);
                //如果原始数据没有值，按时间找最近一个；判断条件：时间不在当前时间点之后
                if (Double.isNaN(sourceDataValue)) {
                    List<Date> lessThanDates = sourceData.keySet().stream().filter(p -> p.after(finalDate) == false).sorted(Comparator.comparingLong(Date::getTime)).collect(Collectors.toList());
                    if (lessThanDates != null && lessThanDates.size() > 0x0) {
                        //小于等于时间点的结果升序排列，取最后一个
                        sourceDataValue = sourceData.get(lessThanDates.get(lessThanDates.size() - 1));
                    }
                }
                //如果原始数据没有值，取原始数据中的第1个值作为默认
                if (Double.isNaN(sourceDataValue)) {
                    sourceDataValue = sourceData.get(sourceData.keySet().toArray()[0x0]);
                }
            }
            //要求必须有原始值
            if (Double.isNaN(sourceDataValue)) {
                System.out.println("处理水库水位库容，stcd=" + stcd + "，date=" + DateUtil.getDateTimeString(date) + " 原始采集值不存在，无法进行当前时间点的水位和库容计算");
                sbResultInfo.append("处理水库水位库容，stcd=" + stcd + "，date=" + DateUtil.getDateTimeString(date) + " 原始采集值不存在，无法进行当前时间点的水位和库容计算。\r\n");
            } else {
                //计算水位、库容                
                double z = 0.0f;
                if (isWaterDepthValue) {
                    z = sourceDataValue + waterHeight;
                } else {
                    z = waterHeight - sourceDataValue;
                }

                //库容的计算使用的水位要使用加高程后的水位值
                double w = (z - waterMin) * wa + wb;
                System.out.println("处理水库水位库容，date=" + DateUtil.getDateTimeString(date) + " 原始采集值=" + sourceDataValue + " z=" + z + " w=" + w);

                //写入数据
                Map<String, Object> whereData = new HashMap<>();
                whereData.put("stcd", stcd);
                whereData.put("tm", date);
                //判断数据是否存在，如果存在仅对数据值和上传标记进行修改；如果重新写入会因为写入时间延后导致统计准确率错误
                if (SqlRunnerUtil.hasExistData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, whereData)) {
                    //已有相同站点、相同时间的数据存在
                    Map<String, Object> updateData = new HashMap<>();
                    updateData.put("z", z);
                    updateData.put("w", w);
                    //updateData.put("intm", now);
                    updateData.put("flag", flag);

                    boolean updateDataResult = SqlRunnerUtil.updateData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, updateData, whereData);
                } else {
                    //不存在相同数据，写入
                    Map<String, Object> insertData = new HashMap<>();
                    insertData.put("rscd", rscd);
                    insertData.put("stcd", stcd);
                    insertData.put("tm", date);
                    insertData.put("z", z);
                    insertData.put("w", w);
                    insertData.put("zrcd", " ");
                    insertData.put("intm", now);
                    insertData.put("flag", flag);
                    if (appid != null) insertData.put("appid", appid);
                    if (appkey != null) insertData.put("appkey", appkey);

                    boolean insertDataResult = SqlRunnerUtil.insertData(destDbUrl, destDbUser, destDbPassword, destDbTableCode, insertData);
                }
            }

            //每个时间点都需要判断是否在汛期，可能出现一次出现4-5月或10-11月的数据情况
            int latestDataMonth = DateUtil.getMonth(date);
            if (latestDataMonth > 10 || latestDataMonth < 5)
                date = DateUtil.addMinutes(date, 60);
            else
                date = DateUtil.addMinutes(date, 5);
        }

        //如果处理结果有警告信息，返回警告信息
        if (sbResultInfo.length() > 0x0)
            return sbResultInfo.toString();

        return null;
    }
}
